package com.bysreg;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.LinkedList;
import java.util.Stack;
import java.io.IOException;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.extractor.WordExtractor;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument;

public class Crawler {
    //kata kunci pencarian
    final private char[][] query;
    
    //lokasi pencarian dimulai
    final private String searchpath;

    //algoritma yang dipakai untuk pencocokan string
    final private int matchalgo;
    public static int KMP = 1;
    public static int BM=2;
    public static int BRUTE=0;

    //algoritma yang dipakai untuk pencarian 
    final private int searchalgo;
    public static int BFS = 0;
    public static int DFS = 1;

    //parameter tambahan untuk pencocokan string
    final private int matchparam;
    public static int TITLE_AND_CONTENT=0;
    public static int TITLE=1;

    //maksimum panjang karakter preview suatu hasil
    public static final int MAX_PREVIEW_CHAR = 154;
    //berapa banyak maksimum kata yang ditampilkan jika ketemu kata kunci
    public static final int MAX_A_MATCH_PREVIEW_CHAR = 90;

    //penampung hasil pencarian
    final LinkedList<Result> res;

    public Crawler(String query,String searchpath,int matchalgo, int searchalgo, int matchparam) {
        String[] squery = query.split(" ");
        this.query = new char[squery.length][];
        for(int i=0;i<squery.length;i++) {
            if(!(squery[i].equalsIgnoreCase("yang") || squery[i].equalsIgnoreCase("dan") || squery[i].equalsIgnoreCase("dengan") || squery[i].equalsIgnoreCase("di") || squery[i].equalsIgnoreCase("atau") || squery[i].equalsIgnoreCase(".") || squery[i].equalsIgnoreCase(","))) {
                this.query[i] = squery[i].toCharArray();            
            }
        }
        this.searchpath = searchpath;
        this.matchalgo = matchalgo;
        this.searchalgo = searchalgo;
        this.matchparam = matchparam;
        res = new LinkedList<Result>();
    }

    public Result[] crawl() {
        if(searchalgo==BFS) {
            bfscrawl();
        }else if(searchalgo==DFS) {
            dfscrawl();
        }
        return res.toArray(new Result[0]);
    }

    private void bfscrawl() {
        LinkedList<File> sfile = new LinkedList<File>();
        sfile.addLast(new File(searchpath));
        File node;
        while(!sfile.isEmpty()) {
            node = sfile.removeFirst();
            if(node.isDirectory()) {
                for(File f : node.listFiles()) {
                    sfile.addLast(f);
                }
            }else{
                match(node,res);
            }
        }
    }

    private void dfscrawl() {
        Stack<File> sfile = new Stack<File>();
        sfile.push(new File(searchpath));
        File node;
        while(!sfile.isEmpty()) {
            node = sfile.pop();
            if(node.isDirectory()) {
                for(File f : node.listFiles()) {
                    sfile.push(f);
                }
            }else{
                match(node,res);
            }
        }
    }

    private void match(File file, LinkedList<Result> res) {                              
        Result ares;        
        StringBuilder preview = new StringBuilder(MAX_PREVIEW_CHAR);        
        StringBuilder title = new StringBuilder(MAX_PREVIEW_CHAR);           
        char[] s = new char[10];        
        try{
            if(getExtension(file).equalsIgnoreCase(".docx")) {
                s = readDocx(file.getPath()).replaceAll("\n", " ").toCharArray();                
            }else {
                s = readFileAsString(file).replaceAll("\n", " ").toCharArray();
            } 
            //iterasi untuk tiap pattern pada query
            for(char[] pattern : query) {                
                if(matchalgo==BRUTE) {                        
                    matchBrute(pattern, s, preview, file.getName().toCharArray(), title);
                }else if(matchalgo==KMP) {                    
                    matchKMP(pattern, s, preview, file.getName().toCharArray(), title);
                }else if(matchalgo==BM){                    
                    matchBM(pattern, s, preview, file.getName().toCharArray(), title);
                }                
                if(preview.length()>=MAX_PREVIEW_CHAR) {
                    break;
                }                
            }          
        }catch(IOException e) {            
        }
        if(matchparam==TITLE_AND_CONTENT && preview.length()>0) {            
            if(title.length()==0) {
                title.append(file.getName());
            }
            ares = new Result(title.toString(),file.getName(),file.getPath(),preview.toString());
            res.add(ares);            
        }else if(matchparam==TITLE && title.length()>0) {
            ares = new Result(title.toString(),file.getName(),file.getPath(),preview.toString());
            res.add(ares);            
        }
    }
    
    //dengan algoritma bruteforce
    private void matchBrute(char[] pattern, char[] text, StringBuilder preview, char[] filename, StringBuilder title) {
        int i=0;//indeks penunjuk teks
        int m=0;//indeks penunjuk pattern
        while(matchparam==TITLE_AND_CONTENT && i<text.length && preview.length()<MAX_PREVIEW_CHAR) {
            if(text[i]==pattern[0]) {
                m=1;                    
                while(i+m<text.length) {
                    if(text[i+m]!=pattern[m]) {                            
                        break;
                    }
                    if(m==pattern.length-1 && text[i+m]==pattern[m]) {                            
                        addPreview(preview,text,i,pattern.length);
                        break;
                    }
                    m++;
                }                    
            }
            i++;
        } 
        //mencari kata kunci di title
        i=0;m=0;
        while(i<filename.length) {
            if(filename[i]==pattern[0]) {
                m=1;                    
                while(i+m<filename.length) {
                    if(filename[i+m]!=pattern[m]) {                            
                        break;
                    }
                    if(m==pattern.length-1 && filename[i+m]==pattern[m]) {                            
                        addPreviewTitle(title,filename,i,pattern.length);
                        break;
                    }
                    m++;
                }                    
            }
            i++;            
        }
    }
    
    //dengan algoritma KMP
    private void matchKMP(char[] pattern, char[] text, StringBuilder preview, char[] filename, StringBuilder title) {
        int[] border = calcBorder(pattern); 
        int i=0;//indeks penunjuk text
        int m=0;//indeks penunjuk pattern        
        while(matchparam==TITLE_AND_CONTENT && i<text.length && preview.length()<MAX_PREVIEW_CHAR) {            
            //System.out.println(i+" " +m);
            if(text[i]==pattern[m]) {
                i++;
                m++;
                if(m>=pattern.length) {//ketemu match                       
                    addPreview(preview,text,i-m,pattern.length);
                    m=0;                    
                }
            }else if(m>0){                
                m = border[m-1];
            }else{
                i++;
            }            
        }
        //mencari title
        i=0;m=0;
        while(i<filename.length && title.length()<MAX_PREVIEW_CHAR) {            
            if(filename[i]==pattern[m]) {
                i++;
                m++;
                if(m>=pattern.length) {//ketemu match                       
                    addPreviewTitle(title,filename,i-m,pattern.length);                    
                    m=0;                    
                }
            }else if(m>0){                
                m = border[m-1];
            }else{
                i++;
            }            
        }
    }
    
    public int[] calcBorder(char[] pattern) {
        int[] border = new int[pattern.length];  
        border[0]=0;
        int m = pattern.length;
        int j=0;
        int i=1;
        while(i<m) {
            if(pattern[i]==pattern[j]) {
                border[i] = j+1;
                i++;
                j++;
            }else if(j>0) {
                j = border[j-1];
            }else {
                border[i] = 0;
                i++;
            }            
        }
        return border;       
    }
    
    //dengan algoritma BM
    private void matchBM(char[] pattern, char[] text, StringBuilder preview, char[] filename, StringBuilder title) {
        int[] fLO = calcLastOcur(pattern);
        int i=pattern.length-1;//indeks penunjuk teks
        int m=pattern.length-1;//indeks penunjuk pattern
        while(matchparam==TITLE_AND_CONTENT && i<text.length && preview.length()<MAX_PREVIEW_CHAR) {            
            //System.out.println(i + " " + m + " " + (int)text[i]);            
            if(text[i]==pattern[m]) {
                i--;
                m--;
                if(m<0) {//ketemu match                                      
                    addPreview(preview,text,i+1,pattern.length);
                    i=i+pattern.length+1;
                    m=pattern.length-1;  
                }
            }else{
                if(((int)text[i])>=128 || fLO[text[i]]==-1) {                    
                    i+=pattern.length;                    
                }else if(fLO[text[i]]<m) {
                    i += pattern.length - (fLO[text[i]]+1);                                        
                }else if(fLO[text[i]]>m) {                   
                    i = i+(pattern.length - (m+1))+1;                    
                }
                m=pattern.length-1;
            }
        }
        i=pattern.length-1;m=pattern.length-1;
        while(i<filename.length && title.length()<MAX_PREVIEW_CHAR) {                        
            if(filename[i]==pattern[m]) {
                i--;
                m--;
                if(m<0) {//ketemu match                                      
                    addPreviewTitle(title,filename,i+1,pattern.length);
                    i=i+pattern.length+1;
                    m=pattern.length-1;  
                }
            }else{
                if((int)filename[i]>128 || fLO[filename[i]]==-1) {                    
                    i+=pattern.length;                    
                }else if(fLO[filename[i]]<m) {
                    i += pattern.length - (fLO[filename[i]]+1);                                        
                }else if(fLO[filename[i]]>m) {                   
                    i = i+(pattern.length - (m+1))+1;                    
                }
                m=pattern.length-1;
            }
        }
    }
    
    public int[] calcLastOcur(char[] pattern) {
        int[] ret = new int[128];        
        for(int i=0;i<128;i++) {
            ret[i] = -1;
        }        
        for(int i=0;i<pattern.length;i++) {
            ret[pattern[i]] = i;
        }        
        return ret;
    }

    private static void addPreview(StringBuilder preview, char[] s, int idx, int count) {
        //jika kata kunci ditemukan di kalimat awal file
        int i;               
        if(idx==0) {//berada di ujung awal teks
            i=idx;            
            while(i<idx+MAX_A_MATCH_PREVIEW_CHAR && i<s.length  && preview.length()+3<MAX_PREVIEW_CHAR) {
                if(i==idx) {
                    preview.append("<b>");
                }else if(i==idx+count) {
                    preview.append("</b>");
                }
                preview.append(s[i]);                
                i++;
            }
            preview.append("...");
        }else if(idx+count==s.length) { //berada di ujung akhir teks            
            int temp = preview.length();
            i=idx+count-1;
            while(i>=idx-(MAX_A_MATCH_PREVIEW_CHAR-count) && i>=0  && preview.length()<MAX_PREVIEW_CHAR) {                
                if(i==idx-1) {
                    preview.insert(temp,"<b>");
                }else if(i==idx+count-1) {
                    preview.insert(temp,"</b>");                    
                }
                preview.insert(temp, s[i]);
                i--;
            }          
        }else{//berada di tengah teks
            //ambil dari kiri dulu abis itu ke kanan
            i=idx+count-(MAX_A_MATCH_PREVIEW_CHAR/2);            
            if((MAX_A_MATCH_PREVIEW_CHAR/2)>(MAX_PREVIEW_CHAR-preview.length())) {//kondisi dimana jumlah karakter tersisa untuk preview lebih kecil dari yang diinginkan
                i = idx+count-(MAX_PREVIEW_CHAR-preview.length());                
            }
            if(i<0) {
                i=0;
            }
            while(i<idx+(MAX_A_MATCH_PREVIEW_CHAR/2) && i<s.length && preview.length()+3<MAX_PREVIEW_CHAR) {
                if(i==idx) {
                    preview.append("<b>");
                }else if(i==idx+count) {
                    preview.append("</b>");
                }
                preview.append(s[i]);
                i++;
            }
            preview.append("...");
        }        
    }

    private static String readFileAsString(File file) throws java.io.IOException{
        byte[] buffer = new byte[(int) file.length()];
        BufferedInputStream f = null;
        try {
            f = new BufferedInputStream(new FileInputStream(file));
            f.read(buffer);
        } finally {
            if (f != null) try { f.close(); } catch (IOException ignored) { }
        }
        return new String(buffer);
    }
    
    //readDocx menghasilkan error jika file yang sedang dibaca oleh java dibuka oleh pengguna
    public static String readDocx(String filename) {        
        try{
            FileInputStream fis = new FileInputStream(filename);
            org.apache.poi.xwpf.extractor.XWPFWordExtractor oleTextExtractor = new XWPFWordExtractor(new XWPFDocument(fis));
            fis.close();
            return oleTextExtractor.getText();
        }catch(IOException e){
            return "";
        }       
    }
    
    public static String getExtension(File file) {
        String filename = file.getName();
        int dotpos = filename.lastIndexOf('.', filename.length()-1);
        if(dotpos>=0) {
            return filename.substring(dotpos);
        }        
        return "";
    }
    
    private void addPreviewTitle(StringBuilder preview, char[] s, int idx, int count) {
        //jika kata kunci ditemukan di kalimat awal file
        int i;               
        if(idx==0) {//berada di ujung awal teks
            i=idx;            
            while(i<idx+MAX_A_MATCH_PREVIEW_CHAR && i<s.length  && preview.length()+3<MAX_PREVIEW_CHAR) {
                if(i==idx) {
                    preview.append("<b>");
                }else if(i==idx+count) {
                    preview.append("</b>");
                }
                preview.append(s[i]);                
                i++;
            }            
        }else if(idx+count==s.length) { //berada di ujung akhir teks            
            int temp = preview.length();
            i=idx+count-1;
            while(i>=idx-(MAX_A_MATCH_PREVIEW_CHAR-count) && i>=0  && preview.length()<MAX_PREVIEW_CHAR) {                
                if(i==idx-1) {
                    preview.insert(temp,"<b>");
                }else if(i==idx+count-1) {
                    preview.insert(temp,"</b>");                    
                }
                preview.insert(temp, s[i]);
                i--;
            }          
        }else{//berada di tengah teks
            //ambil dari kiri dulu abis itu ke kanan
            i=idx+count-(MAX_A_MATCH_PREVIEW_CHAR/2);            
            if((MAX_A_MATCH_PREVIEW_CHAR/2)>(MAX_PREVIEW_CHAR-preview.length())) {//kondisi dimana jumlah karakter tersisa untuk preview lebih kecil dari yang diinginkan
                i = idx+count-(MAX_PREVIEW_CHAR-preview.length());                
            }
            if(i<0) {
                i=0;
            }
            while(i<idx+(MAX_A_MATCH_PREVIEW_CHAR/2) && i<s.length && preview.length()+3<MAX_PREVIEW_CHAR) {
                if(i==idx) {
                    preview.append("<b>");
                }else if(i==idx+count) {
                    preview.append("</b>");
                }
                preview.append(s[i]);
                i++;
            }            
        }        
    }
}
